package cn.fyk.sso.server.filter;

import java.io.IOException;
import java.util.Collection;
import java.util.Iterator;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import cn.fyk.sso.server.cache.TicketCache;
import cn.fyk.sso.server.pojo.User;
import cn.fyk.sso.server.ticket.GrantingTicket;
import cn.fyk.sso.server.ticket.ServiceTicket;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.springframework.util.StringUtils;

import cn.hutool.json.JSONUtil;

/**
 * @Description:	des
 * @Author:		方宇康
 * @CreateDate:	2020/1/7 9:45
 * @Version:      	1.0.0.1
 * @Company:       联通智网科技有限公司
 */
@Slf4j
public class SsoServerFilter implements Filter
{
	private final String LOGIN_URL = "http://localhost:8080/ssoServer/sso/login";

	private final String COOKIE_NAME = "qy-bb-sso";

	private final String VALIDATE_TICKET = "sso/validateTicket";

	private final String SSO_SIGN_IN = "sso/signIn";

	private final String SSO_AUTHENTICATION = "sso/authentication";

	private final String SSO_SIGN_OUT = "sso/signOut";

	private final String USER_NAME = "root";

	@Override
	public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException
	{
		HttpServletRequest request = (HttpServletRequest )servletRequest;
		HttpServletResponse response = (HttpServletResponse )servletResponse;
		String uri = request.getRequestURI(), service = request.getParameter("service");
		// 登录验证
		if (uri.endsWith(this.SSO_SIGN_IN)) {
			this.signInValidate(request, response, service);
			return;
		}
		// 票据验证
		else if (uri.endsWith(this.VALIDATE_TICKET)) {
			this.ticketValidate(request, response, service);
			return;
		}
		// 权限验证
		else if (uri.endsWith(this.SSO_AUTHENTICATION)) {
			this.authentication(request, response, service);
			return;
		}
		// 登录退出
		else if (uri.endsWith(this.SSO_SIGN_OUT)) {
			log.info("SsoServerFilter|doFilter|进入Sso服务端拦截业务处理|登录退出");
			String gtCookie = this.getSSOCookie(request);
			ssoLogout(request, response, gtCookie);
			response.sendRedirect(LOGIN_URL.concat("?service=").concat(service));
			return;
		}
		filterChain.doFilter(servletRequest, servletResponse);
	}

	/**
	 * 登录验证
	 *
	 * @param request
	 * @param response
	 * @param service
	 * @return
	 * @exception
	 * @date        2020/1/6 18:04
	 */
	public void signInValidate(HttpServletRequest request, HttpServletResponse response, String service)
	{
		try {
			log.info("SsoServerFilter|signIn|登录验证|请求入参：={}", service);
			String loginName = request.getParameter("loginName"), password = request.getParameter("password");
			log.info("SsoServerFilter|signIn|登录验证|用户名：={}，用户密码：={}", loginName, password);
			if (this.USER_NAME.equals(loginName)) {
				// 生成票据
				String ticketId = UUID.randomUUID().toString();
				GrantingTicket grantingTicket = new GrantingTicket();
				grantingTicket.setId(ticketId);
				// Mock用户信息
				User user = new User();
				user.setUserId(1L);
				user.setLoginName(loginName);
				user.setPassword(password);
				// 票据与用户绑定
				grantingTicket.setUser(user);
				log.info("SsoServerFilter|signInValidate|登录验证|票据信息：={}，={}", ticketId, grantingTicket);
				// 服务端Cookie，会话级Cookie 关闭浏览器失效
				Cookie cookie = new Cookie("qy-bb-sso", ticketId);
				cookie.setMaxAge(-1);
				cookie.setPath("/");
				response.addCookie(cookie);
				// 缓存存放票据
				TicketCache.putGrantingTicket(ticketId, grantingTicket);
				// 客户端应用标识
				ServiceTicket serviceTicket = new ServiceTicket();
				String stId = UUID.randomUUID().toString();
				serviceTicket.setId(stId);
				serviceTicket.setHost(service);
				log.info("SsoServerFilter|signInValidate|登录验证|客户端应用标识：={}，={}", stId, serviceTicket);

				ConcurrentHashMap<String, ServiceTicket> serviceTicketMap = grantingTicket.getServiceTicketMap();
				serviceTicketMap.put(stId, serviceTicket);
				// 票据与客户端应用标识绑定
				TicketCache.putSTAndGT(stId, ticketId);
				response.sendRedirect(service.concat("?ticket=").concat(serviceTicket.getId()));
			} else {
				response.sendRedirect(LOGIN_URL.concat("?service=").concat(service).concat("&errorMsg=登录名或者密码错误"));
			}
		}
		catch (Exception e) {
			log.error("SsoServerFilter|signIn|登录验证异常", e);
		}
	}

	/**
	 * 票据验证
	 *
	 * @param request
	 * @param response
	 * @param service
	 * @return
	 * @exception
	 * @date        2020/1/7 9:45
	 */
	private void ticketValidate(HttpServletRequest request, HttpServletResponse response, String service)
	{
		try {
			log.info("SsoServerFilter|ticketValidate|进入Sso服务端拦截业务处理|票据验证");
			String ticket = request.getParameter("ticket"), gtId = TicketCache.getGTByST(ticket);
			GrantingTicket grantingTicket = TicketCache.getGrantingTicket(gtId);
			User user = grantingTicket.getUser();
			String jsonStr = JSONUtil.toJsonStr(user);
			// 让ticket过期
			ServiceTicket serviceTicket = grantingTicket.getServiceTicketMap().get(ticket);
			boolean expired = serviceTicket.isExpired();
			if (expired) {
				serviceTicket.setExpired(false);
				response.getOutputStream().write(jsonStr.getBytes("UTF-8"));
			} else {
				response.sendRedirect(LOGIN_URL.concat("?service=").concat(service).concat("&errorMsg=Ticket已经过期"));
			}
		}
		catch (Exception e) {
			log.error("SsoServerFilter|ticketValidate|票据验证异常", e);
		}
	}

	/**
	 * 权限验证
	 *
	 * @param request
	 * @param response
	 * @param service
	 * @return
	 * @exception
	 * @date        2020/1/7 9:54
	 */
	private void authentication(HttpServletRequest request, HttpServletResponse response, String service)
	{
		try {
			log.info("SsoServerFilter|authentication|进入Sso服务端拦截业务处理|权限验证");
			String gtId = this.getSSOCookie(request);
			GrantingTicket grantingTicket = null;
			if(gtId != null) {
				grantingTicket = TicketCache.getGrantingTicket(gtId);
			}
			if(StringUtils.isEmpty(service) || null == gtId || null == grantingTicket){
				response.sendRedirect(LOGIN_URL.concat("?service=").concat(service));
				return;
			}
			ServiceTicket serviceTicket = new ServiceTicket();
			String stId = UUID.randomUUID().toString();
			serviceTicket.setId(stId);
			serviceTicket.setHost(service);
			ConcurrentHashMap<String, ServiceTicket> serviceTicketMap = grantingTicket.getServiceTicketMap();
			serviceTicketMap.put(stId, serviceTicket);
			TicketCache.putSTAndGT(stId, grantingTicket.getId());
			response.sendRedirect(service.concat("?ticket=").concat(serviceTicket.getId()));
		}
		catch (Exception e) {
			log.error("SsoServerFilter|authentication|权限验证异常", e);
		}
	}

	/**
	 * 单点退出
	 *
	 * @param request
	 * @param response
	 * @param gtCookie
	 * @return
	 * @exception
	 * @date        2020/1/6 9:31
	 */
	public void ssoLogout(HttpServletRequest request, HttpServletResponse response, String gtCookie)
	{
		log.info("SsoServerFilter|ssoLogout|单点退出|请求入参Cookie：={}", gtCookie);
		// 清除cookie值
		this.removeSSOCookie(request,response, COOKIE_NAME);
		GrantingTicket grantingTicket = TicketCache.getGrantingTicket(gtCookie);
		log.info("SsoServerFilter|ssoLogout|单点退出|获取票据信息：={}", grantingTicket);
		if (null != grantingTicket) {
			ConcurrentHashMap<String, ServiceTicket> serviceTicketMap = grantingTicket.getServiceTicketMap();
			log.info("SsoServerFilter|ssoLogout|单点退出|获取登录应用信息：={}", gtCookie);
			Collection<ServiceTicket> serviceTickets = serviceTicketMap.values();
			ServiceTicket serviceTicket;
			log.info("SsoServerFilter|ssoLogout|单点退出|将对应用户所有登录信息进行退出操作");
			for (Iterator<ServiceTicket> iterator = serviceTickets.iterator(); iterator.hasNext();) {
				serviceTicket = iterator.next();
				clientLogout(serviceTicket);
				TicketCache.removeServiceTicket(serviceTicket.getId());
			}
		}
		TicketCache.removeGrantingTicket(gtCookie);
	}

	/**
	 * 获得sso的cookie值
	 *
	 * @param request
	 * @return
	 * @exception
	 * @date        2020/1/6 9:31
	 */
	public String getSSOCookie(HttpServletRequest request)
	{
		Cookie[] cookies = request.getCookies();
		String gtCookie = null;
		if(null != cookies)
		{
			Cookie cookie;
			for (int i = 0; i < cookies.length; i++) {
				cookie = cookies[i];
				if(COOKIE_NAME.equals(cookie.getName()))
				{
					gtCookie = cookie.getValue();
					break;
				}
			}
		}
		return gtCookie;
	}

	/**
	 * 清理Cookie
	 *
	 * @param request
	 * @param response
	 * @param name
	 * @return
	 * @exception
	 * @date        2020/1/6 9:32
	 */
	public void removeSSOCookie(HttpServletRequest request, HttpServletResponse response, String name)
	{
		Cookie[] cookies = request.getCookies();
		if (null == cookies) {
			return;
		}
		for (Cookie cookie : cookies) {
			if (cookie.getName().equals(name)) {
				cookie.setValue(null);
				// 立即销毁cookie
				cookie.setMaxAge(0);
				cookie.setPath("/");
				response.addCookie(cookie);
				break;
			}
		}
	}

	/**
	 * 通知客户端退出
	 *
	 * @param serviceTicket
	 * @return
	 * @exception
	 * @date        2020/1/6 9:34
	 */
	public static void clientLogout(ServiceTicket serviceTicket)
	{
		String url = serviceTicket.getHost().concat("?requestLogout=").concat(serviceTicket.getId());
		CloseableHttpClient httpClient = HttpClients.createDefault();
		HttpPost httppost = new HttpPost(url);
		try {
			CloseableHttpResponse execute = httpClient.execute(httppost);
			execute.close();
		} catch (Exception e) {
			log.error("SsoServerFilter|clientLogout|通知客户端退出异常", e);
		} finally {
			try {
				httpClient.close();
			} catch (IOException e) {
				log.error("SsoServerFilter|clientLogout|通知客户端退出资源释放异常", e);
			}
		}
	}
}
